/*
 * Copyright (c) 2001-2008 Caucho Technology, Inc.  All rights reserved.
 *
 * The Apache Software License, Version 1.1
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Caucho Technology (http://www.caucho.com/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
 *    endorse or promote products derived from this software without prior
 *    written permission. For written permission, please contact
 *    info@caucho.com.
 *
 * 5. Products derived from this software may not be called "Resin"
 *    nor may "Resin" appear in their names without prior written
 *    permission of Caucho Technology.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author Scott Ferguson
 */

package com.caucho.hessian.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Abstract output stream for Hessian requests.
 * 
 * <pre>
 * OutputStream os = ...; // from http connection
 * AbstractOutput out = new HessianSerializerOutput(os);
 * String value;
 * 
 * out.startCall("hello");  // start hello call
 * out.writeString("arg1"); // write a string argument
 * out.completeCall();      // complete the call
 * </pre>
 */
abstract public class AbstractHessianOutput {
	// serializer factory
	private SerializerFactory _defaultSerializerFactory;

	// serializer factory
	protected SerializerFactory _serializerFactory;

	private byte[] _byteBuffer;

	/**
	 * Sets the serializer factory.
	 */
	public void setSerializerFactory(SerializerFactory factory) {
		_serializerFactory = factory;
	}

	/**
	 * Gets the serializer factory.
	 */
	public SerializerFactory getSerializerFactory() {
		// the default serializer factory cannot be modified by external
		// callers
		if (_serializerFactory == _defaultSerializerFactory) {
			_serializerFactory = new SerializerFactory();
		}

		return _serializerFactory;
	}

	/**
	 * Gets the serializer factory.
	 */
	protected final SerializerFactory findSerializerFactory() {
		SerializerFactory factory = _serializerFactory;

		if (factory == null) {
			factory = SerializerFactory.createDefault();
			_defaultSerializerFactory = factory;
			_serializerFactory = factory;
		}

		return factory;
	}

	/**
	 * Initialize the output with a new underlying stream.
	 */
	public void init(OutputStream os) {
	}

	/**
	 * Writes a complete method call.
	 */
	public void call(String method, Object[] args) throws IOException {
		int length = args != null ? args.length : 0;

		startCall(method, length);

		for (int i = 0; i < length; i++)
			writeObject(args[i]);

		completeCall();
	}

	/**
	 * Starts the method call:
	 * 
	 * <code><pre>
	 * C
	 * </pre></code>
	 * 
	 * @param method
	 *            the method name to call.
	 */
	abstract public void startCall() throws IOException;

	/**
	 * Starts the method call:
	 * 
	 * <code><pre>
	 * C string int
	 * </pre></code>
	 * 
	 * @param method
	 *            the method name to call.
	 */
	abstract public void startCall(String method, int length) throws IOException;

	/**
	 * For Hessian 2.0, use the Header envelope instead
	 * 
	 * @deprecated
	 */
	public void writeHeader(String name) throws IOException {
		throw new UnsupportedOperationException(getClass().getSimpleName());
	}

	/**
	 * Writes the method tag.
	 * 
	 * <code><pre>
	 * string
	 * </pre></code>
	 * 
	 * @param method
	 *            the method name to call.
	 */
	abstract public void writeMethod(String method) throws IOException;

	/**
	 * Completes the method call:
	 * 
	 * <code><pre>
	 * </pre></code>
	 */
	abstract public void completeCall() throws IOException;

	/**
	 * Writes a boolean value to the stream. The boolean will be written with
	 * the following syntax:
	 * 
	 * <code><pre>
	 * T
	 * F
	 * </pre></code>
	 * 
	 * @param value
	 *            the boolean value to write.
	 */
	abstract public void writeBoolean(boolean value) throws IOException;

	/**
	 * Writes an integer value to the stream. The integer will be written with
	 * the following syntax:
	 * 
	 * <code><pre>
	 * I b32 b24 b16 b8
	 * </pre></code>
	 * 
	 * @param value
	 *            the integer value to write.
	 */
	abstract public void writeInt(int value) throws IOException;

	/**
	 * Writes a long value to the stream. The long will be written with the
	 * following syntax:
	 * 
	 * <code><pre>
	 * L b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre></code>
	 * 
	 * @param value
	 *            the long value to write.
	 */
	abstract public void writeLong(long value) throws IOException;

	/**
	 * Writes a double value to the stream. The double will be written with the
	 * following syntax:
	 * 
	 * <code><pre>
	 * D b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre></code>
	 * 
	 * @param value
	 *            the double value to write.
	 */
	abstract public void writeDouble(double value) throws IOException;

	/**
	 * Writes a date to the stream.
	 * 
	 * <code><pre>
	 * T  b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre></code>
	 * 
	 * @param time
	 *            the date in milliseconds from the epoch in UTC
	 */
	abstract public void writeUTCDate(long time) throws IOException;

	/**
	 * Writes a null value to the stream. The null will be written with the
	 * following syntax
	 * 
	 * <code><pre>
	 * N
	 * </pre></code>
	 * 
	 * @param value
	 *            the string value to write.
	 */
	abstract public void writeNull() throws IOException;

	/**
	 * Writes a string value to the stream using UTF-8 encoding. The string will
	 * be written with the following syntax:
	 * 
	 * <code><pre>
	 * S b16 b8 string-value
	 * </pre></code>
	 * 
	 * If the value is null, it will be written as
	 * 
	 * <code><pre>
	 * N
	 * </pre></code>
	 * 
	 * @param value
	 *            the string value to write.
	 */
	abstract public void writeString(String value) throws IOException;

	/**
	 * Writes a string value to the stream using UTF-8 encoding. The string will
	 * be written with the following syntax:
	 * 
	 * <code><pre>
	 * S b16 b8 string-value
	 * </pre></code>
	 * 
	 * If the value is null, it will be written as
	 * 
	 * <code><pre>
	 * N
	 * </pre></code>
	 * 
	 * @param value
	 *            the string value to write.
	 */
	abstract public void writeString(char[] buffer, int offset, int length) throws IOException;

	/**
	 * Writes a byte array to the stream. The array will be written with the
	 * following syntax:
	 * 
	 * <code><pre>
	 * B b16 b18 bytes
	 * </pre></code>
	 * 
	 * If the value is null, it will be written as
	 * 
	 * <code><pre>
	 * N
	 * </pre></code>
	 * 
	 * @param value
	 *            the string value to write.
	 */
	abstract public void writeBytes(byte[] buffer) throws IOException;

	/**
	 * Writes a byte array to the stream. The array will be written with the
	 * following syntax:
	 * 
	 * <code><pre>
	 * B b16 b18 bytes
	 * </pre></code>
	 * 
	 * If the value is null, it will be written as
	 * 
	 * <code><pre>
	 * N
	 * </pre></code>
	 * 
	 * @param value
	 *            the string value to write.
	 */
	abstract public void writeBytes(byte[] buffer, int offset, int length) throws IOException;

	/**
	 * Writes a byte buffer to the stream.
	 */
	abstract public void writeByteBufferStart() throws IOException;

	/**
	 * Writes a byte buffer to the stream.
	 * 
	 * <code><pre>
	 * b b16 b18 bytes
	 * </pre></code>
	 * 
	 * @param value
	 *            the string value to write.
	 */
	abstract public void writeByteBufferPart(byte[] buffer, int offset, int length) throws IOException;

	/**
	 * Writes the last chunk of a byte buffer to the stream.
	 * 
	 * <code><pre>
	 * b b16 b18 bytes
	 * </pre></code>
	 * 
	 * @param value
	 *            the string value to write.
	 */
	abstract public void writeByteBufferEnd(byte[] buffer, int offset, int length) throws IOException;

	/**
	 * Writes a full output stream.
	 */
	public void writeByteStream(InputStream is) throws IOException {
		writeByteBufferStart();

		if (_byteBuffer == null)
			_byteBuffer = new byte[1024];

		byte[] buffer = _byteBuffer;

		int len;
		while ((len = is.read(buffer, 0, buffer.length)) > 0) {
			if (len < buffer.length) {
				int len2 = is.read(buffer, len, buffer.length - len);

				if (len2 < 0) {
					writeByteBufferEnd(buffer, 0, len);
					return;
				}

				len += len2;
			}

			writeByteBufferPart(buffer, 0, len);
		}

		writeByteBufferEnd(buffer, 0, 0);
	}

	/**
	 * Writes a reference.
	 * 
	 * <code><pre>
	 * Q int
	 * </pre></code>
	 * 
	 * @param value
	 *            the integer value to write.
	 */
	abstract protected void writeRef(int value) throws IOException;

	/**
	 * Removes a reference.
	 */
	public boolean removeRef(Object obj) throws IOException {
		return false;
	}

	/**
	 * Replaces a reference from one object to another.
	 */
	abstract public boolean replaceRef(Object oldRef, Object newRef) throws IOException;

	/**
	 * Adds an object to the reference list. If the object already exists,
	 * writes the reference, otherwise, the caller is responsible for the
	 * serialization.
	 * 
	 * <code><pre>
	 * R b32 b24 b16 b8
	 * </pre></code>
	 * 
	 * @param object
	 *            the object to add as a reference.
	 * 
	 * @return true if the object has already been written.
	 */
	abstract public boolean addRef(Object object) throws IOException;

	/**
	 * @param obj
	 * @return
	 */
	abstract public int getRef(Object obj);

	/**
	 * Resets the references for streaming.
	 */
	public void resetReferences() {
	}

	/**
	 * Writes a generic object to the output stream.
	 */
	abstract public void writeObject(Object object) throws IOException;

	/**
	 * Writes the list header to the stream. List writers will call
	 * <code>writeListBegin</code> followed by the list contents and then call
	 * <code>writeListEnd</code>.
	 * 
	 * <code><pre>
	 * V
	 *   x13 java.util.ArrayList   # type
	 *   x93                       # length=3
	 *   x91                       # 1
	 *   x92                       # 2
	 *   x93                       # 3
	 * &lt;/list>
	 * </pre></code>
	 */
	abstract public boolean writeListBegin(int length, String type) throws IOException;

	/**
	 * Writes the tail of the list to the stream.
	 */
	abstract public void writeListEnd() throws IOException;

	/**
	 * Writes the map header to the stream. Map writers will call
	 * <code>writeMapBegin</code> followed by the map contents and then call
	 * <code>writeMapEnd</code>.
	 * 
	 * <code><pre>
	 * M type (<key> <value>)* Z
	 * </pre></code>
	 */
	abstract public void writeMapBegin(String type) throws IOException;

	/**
	 * Writes the tail of the map to the stream.
	 */
	abstract public void writeMapEnd() throws IOException;

	/**
	 * Writes the object header to the stream (for Hessian 2.0), or a Map for
	 * Hessian 1.0. Object writers will call <code>writeObjectBegin</code>
	 * followed by the map contents and then call <code>writeObjectEnd</code>.
	 * 
	 * <code><pre>
	 * C type int <key>*
	 * C int <value>*
	 * </pre></code>
	 * 
	 * @return true if the object has already been defined.
	 */
	public int writeObjectBegin(String type) throws IOException {
		writeMapBegin(type);

		return -2;
	}

	/**
	 * Writes the end of the class.
	 */
	public void writeClassFieldLength(int len) throws IOException {
	}

	/**
	 * Writes the tail of the object to the stream.
	 */
	public void writeObjectEnd() throws IOException {
	}

	public void writeReply(Object o) throws IOException {
		startReply();
		writeObject(o);
		completeReply();
	}

	public void startReply() throws IOException {
	}

	public void completeReply() throws IOException {
	}

	public void writeFault(String code, String message, Object detail) throws IOException {
	}

	public void flush() throws IOException {
	}

	public void close() throws IOException {
	}
}
